package main;

import java.io.*;
import java.net.*;

//Nikolas Wolfe
//4/3/09
//CNT 4007
//Reliable Data Transfer

public class RDTSender
{
	// constants
	private static final String CLOSE = "close";
	private static final String ACK_0 = "ACK(0)";
	private static final String ACK_1 = "ACK(1)";
	private static final String RECEIVER_HOSTNAME = "rain.cise.ufl.edu";
	private static final String RECEIVER_PORT = "6336";
	
	// sender settings
	private final int port;
	private final String hostname;
	private final BufferedReader inFromUser;
	private final InetAddress IPAddress;
	private boolean terminated;
	private boolean repeat;
	private String input;
	private String expectedACK;
	
	// data fields, sockets
	private byte[] sendData;
	private byte[] receiveData;
	private DatagramSocket senderSocket;
	
	// constructor
	public RDTSender(String host, int port) throws UnknownHostException
	{
		try
		{
			this.hostname = host;
			this.port = port;
			this.inFromUser = new BufferedReader(new InputStreamReader(System.in));
			this.IPAddress = InetAddress.getByName(hostname);
			this.sendData = new byte[1024];
			this.receiveData = new byte[1024];
			this.senderSocket = new DatagramSocket();
			this.terminated = false;
			this.repeat = false;
			this.input = "";
			this.expectedACK = ACK_0;
		}
		catch( SocketException e )
		{
			throw new IllegalStateException("Sever initialization failed!");
		}
	}
	
	public void start()
	{
		while( !isTerminated() )
		{
			try
			{
				if (!isRepeatCycle())
				{
					// send the user input to the server, wait for reply, output
					System.out.print("send>");
					this.input = inFromUser.readLine();
					if (input.trim().equalsIgnoreCase(CLOSE)){
						killSender();
						break;
					}
				}
				
				// send the packet
				rdt_send(input);
				
				// set socket timeout of 10 seconds
				senderSocket.setSoTimeout(10000);
				System.out.print("wait>");
				
				// wait for server response
				DatagramPacket receivePacket = rdt_rcv();
				String retString = convertPacketToString(receivePacket);
				processResponse(retString);
				System.out.println(retString + "\n");
			} 
			catch (SocketTimeoutException e)
			{
				System.out.println("Timeout expired! Resending packet...\n");
				setRepeat(true);
			}
			catch (IOException e)
			{
				System.out.println("IOException! Incorrect input data!");
				killSender();
			}
		}
	}
	
	private void processResponse(String s)
	{
		// We know response will be a huge array of figures, 
		// and the only meaningful ones will be ACK(1) or ACK(0)
		// so chop off the first 5 chars.
		String compare = makeACKString(s);
		if( compare.equals(expectedACK) )
		{
			setRepeat(false);
			if( compare.equals(ACK_0))
			{
				expectedACK = ACK_1;
			}
			else {
				expectedACK = ACK_0;
			}
		}
		else {
			setRepeat(true);
		}
	}
	
	private void rdt_send(String s) throws IOException
	{
		// send the packet
		senderSocket.send(buildPacket(attachReceiverHostname(s)));
	}
	
	private DatagramPacket rdt_rcv() throws IOException
	{
		// wait for network response
		DatagramPacket receivePacket = makeReceivePacket();
		senderSocket.receive(receivePacket);
		return receivePacket;
	}
	
	private String attachReceiverHostname(String input)
	{
		return input + "[" + RECEIVER_HOSTNAME + "," + RECEIVER_PORT + "]";
	}

	private DatagramPacket buildPacket(String s)
	{
		// make string into byte array, construct a packet
		sendData = s.getBytes();
		return new DatagramPacket(sendData, sendData.length, IPAddress, port);
	}
	
	private DatagramPacket makeReceivePacket()
	{
		// construct a new receiver Datagram packet
		return new DatagramPacket(receiveData, receiveData.length);
	}
	
	private String convertPacketToString(DatagramPacket pkt)
	{
		// construct a string from a Datagram packet
		return new String(pkt.getData());
	}
	
	// kill this sender
	private void killSender() 
	{ 
		try
		{
			senderSocket.send((buildPacket(CLOSE)));
			DatagramPacket receivePacket = makeReceivePacket();
			senderSocket.receive(receivePacket);
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		senderSocket.close();
		setTerminated(true);
	}
	
	private String makeACKString(String s)
	{
		char[] arr = s.toCharArray();
		char[] copy = new char[6];
	    System.arraycopy(arr, 0, copy, 0, copy.length);
		String q = new String(copy);
		return q;
	}

	// boolean terminate methods
	private void setTerminated(boolean val)
	{
		this.terminated = val;
	}
	
	private boolean isTerminated()
	{
		return terminated;
	}
	
	// repeat cycle accessor/mutators
	private boolean isRepeatCycle()
	{
		return repeat;
	}
	
	private void setRepeat(boolean val)
	{
		this.repeat = val;
	}
}
